package taskhandler

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/xuri/excelize/v2"

	"cvevulner/common"
	"cvevulner/models"
	"cvevulner/util"
)

var releaseDate map[string]int64

// CveExcel Excel export client
type CveExcel struct {
	ExcelName            string         //excel name
	ExcelHandel          *excelize.File //excel File handle
	CveSheetIdx          int            //CVE sheet index
	CveSheetName         string         // CVE sheet Name
	SecNoticeSheetIdx    int            //Security bulletin index
	SecNoticeSheetName   string         //Security bulletin sheet name
	SecNoticePrefix      string         //Security bulletin prefix
	SecNoticeSuffixIdx   int64          //Security bulletin suffix start value
	InfProductSheetName  string         //CVE Affected products
	InfProductSheetIndex int
	PackageURLSheetName  string
	PackageURLSheetIndex int
}

type IssueAndPkg struct {
	IssueMap map[int64]models.PullRequestIssue
	IssuePkg string
	Repo     string
	IssueId  int64
}

var fillLock sync.Mutex
var wgTrigger sync.WaitGroup

// Init  init excel client
func (ec *CveExcel) Init(excelName, snPrefix string, snSuffix int64) (err error) {
	if excelName == "" || !(strings.HasSuffix(excelName, ".xlsx") || strings.HasSuffix(excelName, "xls")) {
		err = errors.New("excel name illegal")
		return
	}
	ec.ExcelName = excelName
	ec.SecNoticePrefix = snPrefix
	ec.SecNoticeSuffixIdx = snSuffix
	ec.CveSheetIdx = 0
	ec.CveSheetName = "CVE"
	ec.SecNoticeSheetIdx = 1
	ec.SecNoticeSheetName = "安全公告"
	ec.InfProductSheetIndex = 2
	ec.InfProductSheetName = "CVE影响的产品"
	ec.PackageURLSheetIndex = 3
	ec.PackageURLSheetName = "安全公告升级包链接"
	return nil
}

// InitFileHandle Initialize the file handle.
// param forceRewrite is true it means will create a new file otherwise it means append or new.
// the return value wm is 0 for new creation, and 1 for append.
func (ec *CveExcel) InitFileHandle(forceRewrite bool) (wm int8) {
	if forceRewrite {
		ec.ExcelHandel = excelize.NewFile()
		return 0
	}
	s, err := os.Stat(ec.ExcelName)
	if err != nil {
		ec.ExcelHandel = excelize.NewFile()
		return 0
	}
	if !s.IsDir() {
		file, err := excelize.OpenFile(ec.ExcelName)
		if err != nil {
			ec.ExcelHandel = excelize.NewFile()
			return 0
		}
		ec.ExcelHandel = file
		return 1

	}
	ec.ExcelHandel = excelize.NewFile()
	return 0

}

// InitSheet init excel sheet
func (ec *CveExcel) InitSheet() {
	ec.SecNoticeSheetIdx, _ = ec.ExcelHandel.NewSheet(ec.SecNoticeSheetName)
	ec.InfProductSheetIndex, _ = ec.ExcelHandel.NewSheet(ec.InfProductSheetName)
	ec.PackageURLSheetIndex, _ = ec.ExcelHandel.NewSheet(ec.PackageURLSheetName)
	sn := ec.ExcelHandel.GetSheetName(ec.CveSheetIdx)
	ec.ExcelHandel.SetSheetName(sn, ec.CveSheetName)
}

// FillHeader fill the excel sheet header
func (ec *CveExcel) FillHeader() (err error) {
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "A1", "CVE编号")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "A1", "A2")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "B1", "概要")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "B1", "B2")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "C1", "Cvss Score")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "C1", "D1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "c2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "D2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "E1", "Attack Vector")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "E1", "F1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "E2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "F2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "G1", "Attack Complexity")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "G1", "H1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "G2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "H2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "I1", "Privileges Required")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "I1", "J1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "I2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "J2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "K1", "User Interaction")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "K1", "L1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "K2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "L2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "M1", "Scope")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "M1", "N1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "M2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "N2", "OpenEuler")
	if err != nil {
		return
	}

	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "O1", "Confidentiality")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "O1", "P1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "O2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "P2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "Q1", "Integrity")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "Q1", "R1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "Q2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "R2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "S1", "Availability")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "S1", "T1")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "S2", "NVD")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "T2", "OpenEuler")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "U1", "评分类型")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "U1", "U2")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "V1", "安全公告")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "V1", "V2")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.SetCellValue(ec.CveSheetName, "W1", "公告时间")
	if err != nil {
		return
	}
	err = ec.ExcelHandel.MergeCell(ec.CveSheetName, "W1", "W2")
	if err != nil {
		return
	}
	//Security bulletin header generation
	sh := []interface{}{"公告编号", "CVE编号", "简介", "概要", "主题", "描述", "影响组件", "影响产品", "影响包名", "参考", "公告时间"}
	err = ec.ExcelHandel.SetSheetRow(ec.SecNoticeSheetName, "A1", &sh)
	if err != nil {
		return err
	}
	ih := []interface{}{"CVE编号", "影响的产品", "包", "状态"}
	err = ec.ExcelHandel.SetSheetRow(ec.InfProductSheetName, "A1", &ih)
	if err != nil {
		return err
	}
	ph := []interface{}{"包名", "包下载链接"}
	err = ec.ExcelHandel.SetSheetRow(ec.PackageURLSheetName, "A1", &ph)
	if err != nil {
		return err
	}
	return nil
}

func DelFile(fileList []string) {
	if len(fileList) > 0 {
		for _, filex := range fileList {
			err := os.Remove(filex)
			if err != nil {
				logs.Error(err)
			}
		}
	}
}

func affectBrachRep(xmlp *models.ExcelExport, affectBranch string) bool {
	affectBool := false
	if xmlp.AffectProduct != "" && len(xmlp.AffectProduct) > 1 {
		if xmlp.AffectProduct != affectBranch {
			affectBranchsxList := strings.Split(xmlp.AffectProduct, "/")
			for _, ab := range affectBranchsxList {
				if ab == affectBranch {
					affectBool = true
					xmlp.Introduction = strings.ReplaceAll(xmlp.Introduction, xmlp.AffectProduct, affectBranch)
					xmlp.Theme = strings.ReplaceAll(xmlp.Theme, xmlp.AffectProduct, affectBranch)
					xmlp.AffectProduct = affectBranch
					break
				}
			}
		} else {
			affectBool = true
		}
	}
	return affectBool
}

func FindUnaffectBrach(xmlp *models.ExcelExport, affectBranch, accessToken, owner string) bool {
	branchArry, _ := GetBranchesInfo(accessToken, owner, xmlp.OwnedComponent, 1)
	// 包含原因分析字段的新issue，所有分支都要发布
	if xmlp.IsIssueWithAnalysisVersion() {
		return true
	}

	affectBool := false
	if xmlp.AffectedVersion != "" && len(xmlp.AffectedVersion) > 1 {
		affSlice := strings.Split(xmlp.AffectedVersion, ",")
		if len(affSlice) > 0 {
			for _, aff := range affSlice {
				aff = common.BranchVersionRep(aff)
				if strings.Contains(aff, affectBranch+":") || strings.Contains(aff, affectBranch+"：") {
					branchSlice := []string{}
					if strings.Contains(aff, ":") {
						branchSlice = strings.Split(aff, ":")
					} else if strings.Contains(aff, "：") {
						branchSlice = strings.Split(aff, "：")
					}
					if len(branchSlice) == 2 {
						if strings.TrimSpace(branchSlice[0]) == affectBranch &&
							len(strings.TrimSpace(branchSlice[1])) > 1 &&
							strings.TrimSpace(branchSlice[1]) != "受影响" {
							// Query branch information
							for _, arr := range branchArry {
								if arr == strings.TrimSpace(branchSlice[0]) {
									affectBool = true
									break
								}
							}
						}
					}
				}
			}
		}
	}
	return affectBool
}

func (ec *CveExcel) searchValueInSheet(sheetName, value string) (axis string, searched bool) {
	if value == "" {
		return
	}
	sheet, err := ec.ExcelHandel.SearchSheet(sheetName, value)
	if err != nil {
		return
	}
	if len(sheet) > 0 {
		for _, k := range sheet {
			cellValue, _ := ec.ExcelHandel.GetCellValue(sheetName, k)
			if cellValue == value {
				searched = true
				axis = k
				break
			}
		}
		return
	}
	return
}

func (ec *CveExcel) fillCveSheetRow(row []interface{}) (err error) {
	rows, err := ec.ExcelHandel.GetRows(ec.CveSheetName)
	if err != nil {
		return err
	}
	idx := len(rows) + 1
	axis := fmt.Sprintf("A%d", idx)
	err = ec.ExcelHandel.SetSheetRow(ec.CveSheetName, axis, &row)
	return err
}

func (ec *CveExcel) fillSecurityNoticeSheet(row []interface{}) (err error) {
	rows, err := ec.ExcelHandel.GetRows(ec.SecNoticeSheetName)
	if err != nil {
		return err
	}
	idx := len(rows) + 1
	axis := fmt.Sprintf("A%d", idx)
	err = ec.ExcelHandel.SetSheetRow(ec.SecNoticeSheetName, axis, &row)
	return err
}

func (ec *CveExcel) fillAffectProductSheet(row []interface{}) (err error) {
	rows, err := ec.ExcelHandel.GetRows(ec.InfProductSheetName)
	if err != nil {
		return err
	}
	idx := len(rows) + 1
	axis := fmt.Sprintf("A%d", idx)
	err = ec.ExcelHandel.SetSheetRow(ec.InfProductSheetName, axis, &row)
	return err
}

func (ec *CveExcel) fillPackageSheet(row []interface{}) (err error) {
	rows, err := ec.ExcelHandel.GetRows(ec.PackageURLSheetName)
	if err != nil {
		return err
	}
	idx := len(rows) + 1
	axis := fmt.Sprintf("A%d", idx)
	err = ec.ExcelHandel.SetSheetRow(ec.PackageURLSheetName, axis, &row)
	return err
}

// Save save the excel content to file
func (ec *CveExcel) Save(md int8) error {
	if md == 0 {
		return ec.ExcelHandel.SaveAs(ec.ExcelName)
	}
	return ec.ExcelHandel.Save()

}

func getDataUnaffect(startTime string, cves []string) ([]models.IssueTemplate, error) {
	issueTemp, err := models.GetUnffectIssueNumber(startTime, cves)
	if err != nil || issueTemp == nil {
		logs.Info("No unaffected data needs to be processed, startTime: ", startTime)
		return issueTemp, err
	}
	return issueTemp, err
}

func UnaffectIssueProc(affectBranch string, cvrfFileList map[string][]string,
	componentMap map[string]ComponentInfo, startTime, accessToken, owner string,
	unaffectcvrf *UnaffectCvrfSa, unaffectYear int, cves []string) {
	data, err := getDataUnaffect(startTime, cves)
	if len(data) > 0 {
		for _, v := range data {
			// 对于旧数据，仍然按照未完成就不发布的逻辑
			if !v.IsIssueWithAnalysisVersion() && !v.IsIssueComplete() {
				continue
			}

			// 新数据，检查openeuler评分和向量
			if v.IsIssueWithAnalysisVersion() {
				if !v.HasBranch(affectBranch) {
					continue
				}

				if v.HasAffected() {
					commentFunc := func(content string) {
						AddCommentToIssue(content, v.IssueNum, owner, v.Repo, accessToken)
					}

					if !CheckOpenEulerScoreAndVector(&v, commentFunc) {
						logs.Error("CheckOpenEulerScoreAndVector of [%s %s] failed", v.Repo, v.IssueNum)
						continue
					}
				}
			}

			// 发布过且为fixed就不需要发布了
			_, status := IsProductReleased(v.CveNum, v.Repo, affectBranch)
			if status == common.TypeFixed {
				continue
			}

			if len(cves) != 0 && !filterDataInSlice(v.CveNum, cves) {
				continue
			}
			//save data to excel
			el, err := models.GetCanExportExcelData(v.CveNum, v.IssueNum, v.OwnedComponent, v.IssueId)
			if err != nil {
				logs.Error("GetCanExportExcelData, err: ", err, "tpl.CveNum, tpl.IssueNum: ", v.CveNum, v.IssueNum, v.OwnedComponent)
				continue
			}
			for _, vx := range el {
				if filterFixBranch(&vx, vx.CveNum, affectBranch) {
					continue
				}

				// 发布过的除fixed可能被修改，官网数据需要被覆盖，状态一致则忽略
				if vx.AffectType(affectBranch) == status {
					continue
				}

				affectBool := FindUnaffectBrach(&vx, affectBranch, accessToken, owner)
				if affectBool {
					logs.Info("Unaffected version, data: ", vx.CveNum, vx.OwnedComponent, vx.AffectProduct)
					cveNumList := strings.Split(vx.CveNum, "-")
					if cveNumList != nil && len(cveNumList) > 1 {
						cveYears, yearErr := strconv.Atoi(cveNumList[1])
						if yearErr == nil {
							if cveYears <= unaffectYear {
								logs.Error("The unaffected version year is no longer "+
									"processed in the scope, cveYears: ", cveYears, ",unaffectYear: ", unaffectYear)
								continue
							}
						}
					}
					// Generate unaffected cvrf files
					fillLock.Lock()
					BuildUnaffectVulnerabilitySet(unaffectcvrf, vx, affectBranch, componentMap)
					fillLock.Unlock()
				}
			}
		}
	} else {
		logs.Error(err)
	}
}

// filter existing data
func filterDataInSlice(data string, filterList []string) bool {
	for _, v := range filterList {
		if strings.EqualFold(data, v) {
			return true
		}
	}
	return false
}

// if cve exist affected and label exist CVE/FIXED return true
func filterFixBranch(data *models.ExcelExport, cve, branch string) (has bool) {
	if data.IsIssueWithAnalysisVersion() {
		// 包含原因分析字段的新issue，所有分支都要发布
		return false
	}

	has = false
	if !strings.Contains(data.IssueLabel, "CVE/FIXED") {
		return
	}
	if data.AffectedVersion != "" && len(data.AffectedVersion) > 1 {
		versions := strings.Split(data.AffectedVersion, ",")
		for _, v := range versions {
			var branchSlice []string
			if strings.Contains(v, ":") {
				branchSlice = strings.Split(v, ":")
			} else if strings.Contains(v, "：") {
				branchSlice = strings.Split(v, "：")
			}
			if len(branchSlice) == 2 && len(branchSlice[1]) != 0 {
				if strings.EqualFold(branchSlice[0], branch) && branchSlice[1] == "受影响" {
					has = true
					break
				}
			}
		}
	}
	return
}

// CheckOpenEulerScoreAndVector checks if the OpenEulerScore and OpenEulerVector fields of the issue are set.
// If either field is not set, it sends a comment to the issue and returns false.
func CheckOpenEulerScoreAndVector(issue *models.IssueTemplate, comment func(content string)) bool {
	if issue.OpenEulerScore == 0 || issue.OpenEulerVector == "" {
		cc := fmt.Sprintf("@%v CVSS评分和矢量值不能为空", issue.Assignee)
		comment(cc)

		return false
	}

	matched, _ := util.ScoreAndVectorCheck(issue.OpenEulerScore, issue.OpenEulerVector)
	if !matched {
		cc := fmt.Sprintf("@%v CVSS评分和矢量值不一致，请修改至矢量值和CVSS评分一致！", issue.Assignee)
		comment(cc)

		return false
	}

	return true
}

func InitReleaseDate() {
	releaseDate = make(map[string]int64)
	releaseDateConfig := beego.AppConfig.DefaultString("excel::release_date_of_version", "")
	for _, v := range strings.Split(strings.Trim(releaseDateConfig, ";"), ";") {
		split := strings.Split(v, ":")
		releaseDate[split[0]] = util.TimeStrToInt(split[1], "2006-01-02") + 3600*24
	}
}
